﻿using IslandUnit.Exceptions;
using IslandUnit.Providers;
using System.Collections.Generic;
using System;

namespace IslandUnit
{
    /// <summary>
    /// Main class, used to initialize the isolator.
    /// </summary>
    /// <remarks>
    /// Isolator acts as a context, it must be initialized for you can inject dependencies and create mocks and stubbies for your SUT(System under Test).
    /// </remarks>
    /// <example>
    /// Showing how to create and isolator
    /// <code>
    ///     Isolator isolator = new Isolator();
    /// </code>
    /// </example>
    public class Isolator : IDependencySetup, IHideObjectMembers
    {
        private readonly IDictionary<Type, object> _dependencies = new Dictionary<Type, object>();
        internal object Container;

        public Isolator()
        {
            // Initialize IoC container
            this.Container = ProviderRepository.Instance().ContainerProvider.InitializeContainer();
        }

        #region IDependencySetup Members
        
        public Dependency<TDependency> AddDependency<TDependency>() where TDependency : class
        {
            var dependency = new Dependency<TDependency>(this);

            if (this._dependencies.ContainsKey(typeof(TDependency)))
            {
                this._dependencies[typeof(TDependency)] = dependency;
            }
            else
            {
                this._dependencies.Add(typeof(TDependency), dependency);
            }

            return dependency;
        }
        
        public Dependency<TDependency> SetDependency<TDependency>() where TDependency : class
        {
            if (!this._dependencies.ContainsKey(typeof(TDependency)))
            {
                throw new DependencyException(string.Format("Dependency not found: {0}", typeof(TDependency).Name));
            }

            return (Dependency<TDependency>) this._dependencies[typeof(TDependency)];
        }

        public void AddDependencies<TDependency1, TDependency2>()
            where TDependency1 : class
            where TDependency2 : class
        {
            this.AddDependency<TDependency1>();
            this.AddDependency<TDependency2>();
        }

        public void AddDependencies<TDependency1, TDependency2, TDependency3>()
            where TDependency1 : class
            where TDependency2 : class
            where TDependency3 : class
        {
            this.AddDependencies<TDependency1, TDependency2>();
            this.AddDependency<TDependency3>();
        }

        public void AddDependencies<TDependency1, TDependency2, TDependency3, TDependency4>()
            where TDependency1 : class
            where TDependency2 : class
            where TDependency3 : class
            where TDependency4 : class
        {
            this.AddDependencies<TDependency1, TDependency2, TDependency3>();
            this.AddDependency<TDependency4>();
        }

        public void AddDependencies<TDependency1, TDependency2, TDependency3, TDependency4, TDependency5>()
            where TDependency1 : class
            where TDependency2 : class
            where TDependency3 : class
            where TDependency4 : class
            where TDependency5 : class
        {
            this.AddDependencies<TDependency1, TDependency2, TDependency3, TDependency4>();
            this.AddDependency<TDependency5>();
        }

        #endregion

        public void RemoveDependency<TDependency>() where TDependency : class
        {
            this._dependencies.Remove(typeof(TDependency));
        }

        public void ClearDependencies()
        {
            this._dependencies.Clear();
        }

        public TDependency GetDependency<TDependency>() where TDependency : class
        {
            if (!this._dependencies.ContainsKey(typeof(TDependency)))
            {
                throw new DependencyException(string.Format("Dependency not found: {0}", typeof(TDependency).Name));
            }
            
            var dependency = (Dependency<TDependency>) this._dependencies[typeof (TDependency)];

            return dependency.Object;
        }

        public TSut CreateSut<TSut>()
        {
            // Ask the container to create the sut providing all dependencies
            return ProviderRepository.Instance().ContainerProvider.Get<TSut>(this.Container);
        }

        public void VerifyMockExpectations()
        {
            foreach (var dependency in this._dependencies)
            {
                var mockVerification = dependency.Value as IMockVerification;
                
                if (mockVerification != null)
                {
                    mockVerification.VerifyMockExpectations();
                }
            }
        }
    }
}
